package com.uduemc.biso.node.web.api.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.NumberUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.PageInfo;
import com.uduemc.biso.core.extities.center.Host;
import com.uduemc.biso.core.extities.center.Site;
import com.uduemc.biso.core.extities.node.custom.LoginNode;
import com.uduemc.biso.core.utils.JsonResult;
import com.uduemc.biso.core.utils.RedisUtil;
import com.uduemc.biso.node.core.entities.HBackup;
import com.uduemc.biso.node.core.entities.HBackupSetup;
import com.uduemc.biso.node.core.node.config.OperateLoggerStaticModel;
import com.uduemc.biso.node.core.node.config.OperateLoggerStaticModelAction;
import com.uduemc.biso.node.core.operate.entities.OperateLog;
import com.uduemc.biso.node.core.property.GlobalProperties;
import com.uduemc.biso.node.core.utils.SitePathUtil;
import com.uduemc.biso.node.web.api.component.RequestHolder;
import com.uduemc.biso.node.web.api.dto.RequestHostBackupRestoreAmBackupList;
import com.uduemc.biso.node.web.api.dto.RequestHostBackupRestoreHmBackupList;
import com.uduemc.biso.node.web.api.pojo.ResponseBackupSetup;
import com.uduemc.biso.node.web.api.pojo.ResponseHostIsRestore;
import com.uduemc.biso.node.web.api.service.*;
import com.uduemc.biso.node.web.component.operate.OperateLoggerController;
import com.uduemc.biso.node.web.service.OperateLoggerService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

@Service
@Slf4j
public class HostBackupRestoreServiceImpl implements HostBackupRestoreService {

    public static final String TMP_HOST_BACKUP = "TMP:HOST_BACKUP:";

    public static final String TMP_HOST_RESTORE = "TMP:HOST_RESTORE:";

    public static final long HOST_RESTORE_EXPIRE = 3600;

    @Resource
    private RequestHolder requestHolder;

    @Resource
    private GlobalProperties globalProperties;

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private HBackupService hBackupServiceImpl;

    @Resource
    private HBackupSetupService hBackupSetupServiceImpl;

    @Resource
    private OperateLoggerService operateLoggerServiceImpl;

    @Resource
    private AsyncHostBackupRestoreService asyncHostBackupRestoreServiceImpl;

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private WebSiteService webSiteServiceImpl;

    @Override
    public JsonResult nextBackupDate() throws IOException {
        Long hostId = requestHolder.getHost().getId();
        return nextBackupDate(hostId);
    }

    @Override
    public JsonResult nextBackupDate(long hostId) throws IOException {
        HBackupSetup hBackupSetup = hBackupSetupServiceImpl.findByHostIdIfNotAndCreate(hostId);
        LocalDate nextLocalDate = nextBackupDateNotToday(hBackupSetup);
        return JsonResult.ok(DateUtil.format(DateUtil.date(nextLocalDate), "yyyy-MM-dd"));
    }

    @Override
    public JsonResult nextBackupDateByParam(ResponseBackupSetup backupSetup) {
        LocalDate nextLocalDate = nextBackupDateNotToday(backupSetup);
        return JsonResult.ok(DateUtil.format(DateUtil.date(nextLocalDate), "yyyy-MM-dd"));
    }

    @Override
    public JsonResult defaultBackupFilename() throws IOException {
        Long hostId = requestHolder.getHost().getId();
        return defaultBackupFilename(hostId);
    }

    @Override
    public JsonResult defaultBackupFilename(long hostId) throws IOException {
        String filename = "网站备份" + DateUtil.format(DateUtil.date(), "MMdd");
        // 获取这个名称存在的备份数据总量
        int total = hBackupServiceImpl.totalByHostIdFilenameAndType(hostId, filename + "-", (short) -1);
        return JsonResult.ok(filename + "-" + (total + 1));
    }

    @Override
    public JsonResult hmBackupList(RequestHostBackupRestoreHmBackupList hostBackupRestoreHmBackupList) throws IOException {
        Long hostId = requestHolder.getHost().getId();
        return hmBackupList(hostId, hostBackupRestoreHmBackupList);
    }

    @Override
    public JsonResult hmBackupList(long hostId, RequestHostBackupRestoreHmBackupList hostBackupRestoreHmBackupList) throws IOException {
        String filename = hostBackupRestoreHmBackupList.getFilename();
        int pageNum = hostBackupRestoreHmBackupList.getPageNum();
        int pageSize = hostBackupRestoreHmBackupList.getPageSize();
        PageInfo<HBackup> data = hBackupServiceImpl.findNotDelPageInfoAll(hostId, filename, (short) 2, pageNum, pageSize);

        Map<String, Object> result = new HashMap<>();
        result.put("total", data.getTotal());
        result.put("rows", data.getList());
        return JsonResult.ok(result);
    }

    @Override
    public JsonResult amBackupList(RequestHostBackupRestoreAmBackupList hostBackupRestoreAmBackupList) throws IOException {
        Long hostId = requestHolder.getHost().getId();
        return amBackupList(hostId, hostBackupRestoreAmBackupList);
    }

    @Override
    public JsonResult amBackupList(long hostId, RequestHostBackupRestoreAmBackupList hostBackupRestoreAmBackupList) throws IOException {
        String filename = hostBackupRestoreAmBackupList.getFilename();
        int pageNum = hostBackupRestoreAmBackupList.getPageNum();
        int pageSize = hostBackupRestoreAmBackupList.getPageSize();
        PageInfo<HBackup> data = hBackupServiceImpl.findNotDelPageInfoAll(hostId, filename, (short) 1, pageNum, pageSize);

        Map<String, Object> result = new HashMap<>();
        result.put("total", data.getTotal());
        result.put("rows", data.getList());
        return JsonResult.ok(result);
    }

    @Override
    public JsonResult backupSetup() throws IOException {
        Long hostId = requestHolder.getHost().getId();
        return backupSetup(hostId);
    }

    @Override
    public JsonResult backupSetup(long hostId) throws IOException {
        HBackupSetup data = hBackupSetupServiceImpl.findByHostIdIfNotAndCreate(hostId);

        ResponseBackupSetup responseBackupSetup = new ResponseBackupSetup();
        responseBackupSetup.setType(data.getType()).setRetain(data.getRetain()).setStartAt(DateUtil.format(data.getStartAt(), "yyyy-MM-dd"))
                .setMonthly(data.getMonthly()).setWeekly(data.getWeekly()).setDaily(data.getDaily());
        return JsonResult.ok(responseBackupSetup);
    }

    @Override
    public JsonResult updateBackupSetup(ResponseBackupSetup backupSetup) throws IOException {
        Long hostId = requestHolder.getHost().getId();
        return updateBackupSetup(hostId, backupSetup);
    }

    @Override
    public JsonResult updateBackupSetup(long hostId, ResponseBackupSetup backupSetup) throws IOException {
        HBackupSetup data = hBackupSetupServiceImpl.findByHostIdIfNotAndCreate(hostId);
        DateTime parse = DateUtil.parse(backupSetup.getStartAt());
        LocalDate localDate = parse.toLocalDateTime().toLocalDate();
        data.setType(backupSetup.getType()).setRetain(backupSetup.getRetain()).setStartAt(DateUtil.date(localDate).toJdkDate())
                .setMonthly(backupSetup.getMonthly()).setWeekly(backupSetup.getWeekly()).setDaily(backupSetup.getDaily());

        HBackupSetup update = hBackupSetupServiceImpl.updateByPrimaryKey(data);
        if (update == null) {
            return JsonResult.assistance();
        }
        return backupSetup(hostId);
    }

    @Override
    public String backupRedisKey(long hostId) {
        return TMP_HOST_BACKUP + hostId;
    }

    @Override
    public String restoreRedisKey(long hostId) {
        return TMP_HOST_RESTORE + hostId;
    }

    @Override
    synchronized public JsonResult backup(String filename, String description) throws IOException {
        Host host = requestHolder.getHost();
        Long hostId = host.getId();
        String key = backupRedisKey(hostId);
        // 首先验证是否存在被执行的备份操作
        Integer ext = (Integer) redisUtil.get(key);
        if (ext != null && ext == 1) {
            return JsonResult.messageSuccess("正在执行备份，稍后查看手动备份数据列表！");
        }

        // 写日志
        OperateLog backupLog = backupLog(filename, description);
        if (backupLog == null) {
            return JsonResult.assistance();
        }

        // 首先写入备份的数据
        HBackup hBackup = new HBackup();
        hBackup.setHostId(hostId).setType((short) 2).setFilename(filename).setFilepath("").setSize(0L).setDescription(description).setStatus((short) 0)
                .setBackupErrmsg("");
        HBackup insertHBackup;
        try {
            insertHBackup = hBackupServiceImpl.insert(hBackup);
        } catch (IOException e) {
            log.error("hostId:" + hostId + " 执行备份操作失败，" + e.getMessage());
            return JsonResult.assistance();
        }
        if (insertHBackup == null) {
            log.error("hostId:" + hostId + " 插入 HBackup 数据返回结果为空，hBackup:" + hBackup);
            return JsonResult.assistance();
        }

        // redis 写入正在备份状态，备份完成后状态变更，同时根据host进行区分备份的状态
        redisUtil.set(key, 1, 1800);

        AtomicReference<HBackup> atomicAtomicReference = new AtomicReference<>(insertHBackup);
        Consumer<File> con = file -> {
            HBackup atomicHBackup = atomicAtomicReference.get();
            if (file != null && file.isFile()) {
                backupLog.setStatus((short) 1);
                atomicHBackup.setStatus((short) 1).setSize(FileUtil.size(file));
                String basePath = globalProperties.getSite().getBasePath();
                atomicHBackup.setFilepath(FileUtil.subPath(SitePathUtil.getUserPathByCode(basePath, host.getRandomCode()), file));
            } else {
                backupLog.setStatus((short) 3);
                atomicHBackup.setStatus((short) 3).setSize(0L).setFilepath("").setBackupErrmsg("备份失败，请联系管理员！");
            }
            try {
                operateLoggerServiceImpl.update(backupLog);
                hBackupServiceImpl.updateByPrimaryKey(atomicHBackup);
            } catch (IOException e) {
                log.error("更新备份的操作日志以及备份数据的状态遇到问题：" + e.getMessage());
            } finally {
                redisUtil.del(key);
            }
        };

        insertHBackup.setStatus((short) 2);
        hBackupServiceImpl.updateByPrimaryKey(insertHBackup);
        // 将对应的参数传递给异步备份的方法中，执行备份操作。
        asyncHostBackupRestoreServiceImpl.backup(requestHolder.getHost(), atomicAtomicReference.get(), con);

        return JsonResult.messageSuccess("正在执行备份，稍后查看手动备份数据列表！");
    }

    @Override
    synchronized public JsonResult restore(long hBackupId) throws IOException {
        Host host = requestHolder.getHost();
        Long hostId = host.getId();
        String key = restoreRedisKey(hostId);
        // 首先验证是否存在被执行的还原操作
        Integer redisHBackupId = (Integer) redisUtil.get(key);
        if (redisHBackupId != null && redisHBackupId > 0) {
            return JsonResult.messageSuccess("正在执行数据还原操作！");
        }

        HBackup hBackup;
        try {
            hBackup = hBackupServiceImpl.findByHostIdAndId(hBackupId, hostId);
        } catch (IOException e) {
            log.error("hostId:" + hostId + " 执行还原操作失败，" + e.getMessage());
            return JsonResult.assistance();
        }
        if (hBackup == null) {
            log.error("hostId:" + hostId + " ，通过 hBackupId: " + hBackupId + " 未能获取到 HBackup 数据。");
            return JsonResult.assistance();
        }

        // 写日志
        OperateLog restoreLog = restoreLog(hBackup);
        if (restoreLog == null) {
            return JsonResult.assistance();
        }

        // redis 写入正在备份状态，备份完成后状态变更，同时根据host进行区分备份的状态
        redisUtil.set(key, hBackup.getId(), HOST_RESTORE_EXPIRE);

        Consumer<Boolean> con = bool -> {
            if (bool) {
                restoreLog.setStatus((short) 1);
            } else {
                restoreLog.setStatus((short) 3);
            }
            try {
                operateLoggerServiceImpl.update(restoreLog);
            } catch (IOException e) {
                log.error("更新还原备份的操作日志状态遇到问题：" + e.getMessage());
            } finally {
                try {
                    webSiteServiceImpl.cacheCurrentSiteClear();
                } catch (IOException e) {
                    log.error("还原备份操作完毕后清楚前台缓存遇到问题：" + e.getMessage());
                }
                redisUtil.del(key);
            }
        };

        // 将对应的参数传递给异步备份的方法中，执行备份操作。
        asyncHostBackupRestoreServiceImpl.restore(requestHolder.getHost(), hBackup, con);

        return JsonResult.messageSuccess("正在执行数据还原操作！");
    }

    @Override
    public JsonResult isrestore() {
        Host host = requestHolder.getHost();
        Long hostId = host.getId();
        String key = restoreRedisKey(hostId);

        Integer redisHBackupId = (Integer) redisUtil.get(key);
        if (redisHBackupId == null || redisHBackupId < 1) {
            return JsonResult.ok(ResponseHostIsRestore.noRuning());
        }

        HBackup hBackup;
        try {
            hBackup = hBackupServiceImpl.findByHostIdAndId(redisHBackupId, hostId);
        } catch (IOException e) {
            return JsonResult.assistance();
        }
        if (hBackup == null) {
            return JsonResult.assistance();
        }

        long expire = redisUtil.getExpire(key);
        return JsonResult.ok(ResponseHostIsRestore.doRuning(hBackup.getFilename(), HOST_RESTORE_EXPIRE - expire));
    }

    @Override
    public JsonResult deleteBackup(long hBackupId) throws IOException {
        Host host = requestHolder.getHost();
        Long hostId = host.getId();
        HBackup hBackup;
        try {
            hBackup = hBackupServiceImpl.findByHostIdAndId(hBackupId, hostId);
        } catch (IOException e) {
            return JsonResult.assistance();
        }
        if (hBackup == null) {
            return JsonResult.illegal();
        }

        HBackup delete = hBackupServiceImpl.delete(host, hBackup);
        if (delete == null) {
            return JsonResult.assistance();
        }

        return JsonResult.messageSuccess("删除 " + hBackup.getFilename() + " 备份数据成功！", hBackup);
    }

    protected OperateLog backupLog(String filename, String description) throws IOException {
        String declaringTypeName = "com.uduemc.biso.node.web.api.controller.HostController";
        String modelName = OperateLoggerStaticModel.findModelName(declaringTypeName);
        String modelAction = OperateLoggerStaticModel.findModelAction(declaringTypeName, OperateLoggerStaticModelAction.HOST_BACKUP);

        List<Site> sites = requestHolder.getSites();
        String content = "手动备份，对整个网站的所有数据进行备份操作。备份名：" + filename;
        if (CollUtil.size(sites) > 1) {
            content = "手动备份，对整个网站的所有语言版本的数据进行备份操作。备份名：" + filename;
        }

        short status = 2;

        Long hostId = requestHolder.getHost().getId();
        Long siteId = requestHolder.getCurrentSite().getId();
        Integer languageId = requestHolder.getCurrentSite().getLanguageId();
        LoginNode loginNode = requestHolder.getCurrentLoginNode();
        Long loginUserId = loginNode.getLoginUserId();
        String loginUserType = loginNode.getLoginUserType();
        String loginUserName = loginNode.getLoginUserName();
        String ipAddress = OperateLoggerController.ipAddress(requestHolder.getCurrentRequest());

        Map<String, Object> param = new HashMap<>();
        param.put("filename", filename);
        param.put("description", description);

        OperateLog operateLog = new OperateLog();
        operateLog.setHostId(hostId).setSiteId(siteId).setLanguageId(languageId).setUserId(loginUserId).setUserType(loginUserType).setUsername(loginUserName)
                .setModelName(modelName).setModelAction(modelAction).setContent(content).setParam(objectMapper.writeValueAsString(param)).setReturnValue("")
                .setOperateItem("").setStatus(status).setIpaddress(ipAddress);

        return operateLoggerServiceImpl.insert(operateLog);
    }

    protected OperateLog restoreLog(HBackup hBackup) throws IOException {
        String declaringTypeName = "com.uduemc.biso.node.web.api.controller.HostController";
        String modelName = OperateLoggerStaticModel.findModelName(declaringTypeName);
        String modelAction = OperateLoggerStaticModel.findModelAction(declaringTypeName, OperateLoggerStaticModelAction.HOST_RESTORE);

        String content = "网站所有数据进行还原操作，还原 " + DateUtil.formatDate(hBackup.getCreateAt()) + " 备份的数据（" + hBackup.getFilename() + "）。";

        short status = 2;
        Long hostId = requestHolder.getHost().getId();
        Long siteId = requestHolder.getCurrentSite().getId();
        Integer languageId = requestHolder.getCurrentSite().getLanguageId();
        LoginNode loginNode = requestHolder.getCurrentLoginNode();
        Long loginUserId = loginNode.getLoginUserId();
        String loginUserType = loginNode.getLoginUserType();
        String loginUserName = loginNode.getLoginUserName();
        String ipAddress = OperateLoggerController.ipAddress(requestHolder.getCurrentRequest());

        OperateLog operateLog = new OperateLog();
        operateLog.setHostId(hostId).setSiteId(siteId).setLanguageId(languageId).setUserId(loginUserId).setUserType(loginUserType).setUsername(loginUserName)
                .setModelName(modelName).setModelAction(modelAction).setContent(content).setParam(objectMapper.writeValueAsString(hBackup)).setReturnValue("")
                .setOperateItem("").setStatus(status).setIpaddress(ipAddress);

        return operateLoggerServiceImpl.insert(operateLog);
    }

    /**
     * type == 1，上一次备份的日期，如果上一次的时间小于开始时间返回null
     *
     * @param startAt 开始时间
     * @param monthly 月周期
     * @return 上一次备份的日期
     */
    public static LocalDate lastMonthBackupDate(Date startAt, int monthly) {
        if (startAt == null) {
            return null;
        }
        LocalDate nextMonthBackupDate = nextMonthBackupDate(startAt, monthly);
        DateTime lastMonth = DateUtil.offsetMonth(DateUtil.date(nextMonthBackupDate), -1);
        if (DateUtil.compare(lastMonth, startAt) < 0) {
            return null;
        }
        return lastMonth.toLocalDateTime().toLocalDate();
    }

    /**
     * type == 2，上一次备份的日期，如果上一次的时间小于开始时间返回null
     *
     * @param startAt 开始时间
     * @param weekly  周周期
     * @return 上一次备份的日期
     */
    public static LocalDate lastWeekBackupDate(Date startAt, int weekly) {
        if (startAt == null) {
            return null;
        }
        LocalDate nextWeekBackupDate = nextWeekBackupDate(startAt, weekly);
        DateTime lastWeek = DateUtil.offsetDay(DateUtil.date(nextWeekBackupDate), -7);

        if (DateUtil.compare(lastWeek, startAt) < 0) {
            return null;
        }
        return lastWeek.toLocalDateTime().toLocalDate();
    }

    /**
     * type == 3，上一次备份的日期，如果上一次的时间小于今天返回null，如果是今天则返回今天
     *
     * @param startAt 开始时间
     * @param daily   天周期
     * @return 上一次备份的日期
     */
    public static LocalDate lastCustomBackupDate(Date startAt, int daily) {
        if (startAt == null) {
            return null;
        }
        LocalDate nextBackupDate = nextCustomBackupDate(startAt, daily);
        DateTime lastDay = DateUtil.offsetDay(DateUtil.date(nextBackupDate), -daily);

        if (DateUtil.compare(lastDay, startAt) < 0) {
            return null;
        }
        return lastDay.toLocalDateTime().toLocalDate();
    }

    /**
     * type == 1，月周期备份方式，根据开始时间以及日期，计算下一次备份的时间，如果是今天则返回今天。
     *
     * @param startAt 开始时间
     * @param monthly 月周期
     * @return 下一次备份的时间
     */
    public static LocalDate nextMonthBackupDate(Date startAt, int monthly) {
        if (startAt == null) {
            return null;
        }
        DateTime startDateTime = DateUtil.date(DateUtil.date(startAt).toLocalDateTime().toLocalDate());
        DateTime todayDateTime = DateUtil.date(DateUtil.date().toLocalDateTime().toLocalDate());
        if (DateUtil.compare(todayDateTime, startDateTime) <= 0) {
            // 今天已过开始时间，则按照今天时间的当月或者一下月
            int dayOfMonth = startDateTime.toLocalDateTime().toLocalDate().getDayOfMonth();
            if (dayOfMonth == monthly) {
                // 开始时间当天
                return startDateTime.toLocalDateTime().toLocalDate();
            } else if (dayOfMonth < monthly) {
                // 开始时间的本月
                return DateUtil.offsetDay(startDateTime, monthly - dayOfMonth).toLocalDateTime().toLocalDate();
            } else {
                // 开始时间的下一月
                DateTime nextMonth = DateUtil.offsetMonth(startDateTime, 1);
                return DateUtil.offsetDay(nextMonth, monthly - dayOfMonth).toLocalDateTime().toLocalDate();
            }
        } else {
            // 今天已过开始时间，则按照今天时间的当月或者一下月
            int dayOfMonth = todayDateTime.toLocalDateTime().toLocalDate().getDayOfMonth();
            if (dayOfMonth == monthly) {
                // 开始时间当天
                return todayDateTime.toLocalDateTime().toLocalDate();
            } else if (dayOfMonth < monthly) {
                // 开始时间的本月
                return DateUtil.offsetDay(todayDateTime, monthly - dayOfMonth).toLocalDateTime().toLocalDate();
            } else {
                // 开始时间的下一月
                DateTime nextMonth = DateUtil.offsetMonth(todayDateTime, 1);
                return DateUtil.offsetDay(nextMonth, monthly - dayOfMonth).toLocalDateTime().toLocalDate();
            }
        }
    }

    /**
     * type == 2，周周期备份方式，根据开始时间以及周几，计算下一次备份的时间，如果是今天则返回今天。
     *
     * @param startAt 开始时间
     * @param weekly  周周期
     * @return 下一次备份的时间
     */
    public static LocalDate nextWeekBackupDate(Date startAt, int weekly) {
        if (startAt == null) {
            return null;
        }
        DateTime startDateTime = DateUtil.date(DateUtil.date(startAt).toLocalDateTime().toLocalDate());
        DateTime todayDateTime = DateUtil.date(DateUtil.date().toLocalDateTime().toLocalDate());
        if (DateUtil.compare(todayDateTime, startDateTime) <= 0) {
            // 今天还未到开始时间，则按照开始时间的当周或者一下周
            DayOfWeek dayOfWeek = startDateTime.toLocalDateTime().toLocalDate().getDayOfWeek();
            if (dayOfWeek.getValue() == weekly) {
                // 开始时间当天
                return startDateTime.toLocalDateTime().toLocalDate();
            } else if (dayOfWeek.getValue() < weekly) {
                // 开始时间的本周
                return DateUtil.offsetDay(startDateTime, weekly - dayOfWeek.getValue()).toLocalDateTime().toLocalDate();
            } else {
                // 开始时间的下一周
                return DateUtil.offsetDay(startDateTime, 7 + weekly - dayOfWeek.getValue()).toLocalDateTime().toLocalDate();
            }
        } else {
            // 今天已过开始时间，则按照今天时间的当周或者一下周
            DayOfWeek dayOfWeek = todayDateTime.toLocalDateTime().toLocalDate().getDayOfWeek();
            if (dayOfWeek.getValue() == weekly) {
                // 开始时间当天
                return todayDateTime.toLocalDateTime().toLocalDate();
            } else if (dayOfWeek.getValue() < weekly) {
                // 开始时间的本周
                return DateUtil.offsetDay(todayDateTime, weekly - dayOfWeek.getValue()).toLocalDateTime().toLocalDate();
            } else {
                // 开始时间的下一周
                return DateUtil.offsetDay(todayDateTime, 7 + weekly - dayOfWeek.getValue()).toLocalDateTime().toLocalDate();
            }
        }
    }

    /**
     * type == 3，自定义备份方式，根据开始时间以及自定义周期，计算下一次备份的时间，如果是今天则返回今天。
     *
     * @param startAt 开始时间
     * @param daily   天周期
     * @return 下一次备份的时间
     */
    public static LocalDate nextCustomBackupDate(Date startAt, int daily) {
        if (startAt == null) {
            return null;
        }
        DateTime startDateTime = DateUtil.date(DateUtil.date(startAt).toLocalDateTime().toLocalDate());
        DateTime endDateTime = DateUtil.date(DateUtil.date().toLocalDateTime().toLocalDate());

        // 如果当前的时间小于开始的时间，则直接返回开始的时间
        if (DateUtil.compare(endDateTime, startDateTime) <= 0) {
            return startDateTime.toLocalDateTime().toLocalDate();
        }

        long betweenDay = DateUtil.betweenDay(startDateTime, endDateTime, false);
        if (betweenDay < daily) {
            DateTime offsetDay = DateUtil.offsetDay(endDateTime, NumberUtil.parseInt("" + (daily - betweenDay)));
            return offsetDay.toLocalDateTime().toLocalDate();
        }
        int parseInt = NumberUtil.parseInt("" + (betweenDay % daily));
        if (parseInt == 0) {
            return endDateTime.toLocalDateTime().toLocalDate();
        }
        DateTime offsetDay = DateUtil.offsetDay(endDateTime, daily - parseInt);
        LocalDateTime localDateTime = offsetDay.toLocalDateTime();
        return localDateTime.toLocalDate();
    }

    /**
     * type == 1，月周期备份方式，根据开始时间以及每月固定备份日期，计算下一次备份的时间，如果是今天则返回下一次的时间。
     *
     * @param startAt 开始时间
     * @param monthly 月周期
     * @return 下一次备份的时间
     */
    public static LocalDate nextMonthBackupDateNotToday(Date startAt, Integer monthly) {
        if (startAt == null) {
            return null;
        }
        monthly = monthly == null ? 0 : monthly;
        if (monthly < 1) {
            return null;
        }
        LocalDate nextMonthBackupDate = nextMonthBackupDate(startAt, monthly);
        // 今天
        LocalDate todayLocalDate = DateUtil.date().toLocalDateTime().toLocalDate();
        // 如果是今天则返回下个月
        if (DateUtil.compare(DateUtil.date(nextMonthBackupDate), DateUtil.date(todayLocalDate)) == 0) {
            DateTime offsetMonth = DateUtil.offsetMonth(DateUtil.date(nextMonthBackupDate), 1);
            return offsetMonth.toLocalDateTime().toLocalDate();
        }
        return nextMonthBackupDate;
    }

    /**
     * type == 2，周周期备份方式，根据开始时间以及每周固定备份星期，计算下一次备份的时间，如果是今天则返回下一次的时间。
     *
     * @param startAt 开始时间
     * @param weekly  周周期
     * @return 下一次备份的时间
     */
    public static LocalDate nextWeekBackupDateNotToday(Date startAt, Integer weekly) {
        if (startAt == null) {
            return null;
        }
        weekly = weekly == null ? 0 : weekly;
        if (weekly < 1) {
            return null;
        }

        LocalDate nextWeekBackupDate = nextWeekBackupDate(startAt, weekly);
        // 今天
        LocalDate todayLocalDate = DateUtil.date().toLocalDateTime().toLocalDate();
        // 如果是今天则返回下个月
        if (DateUtil.compare(DateUtil.date(nextWeekBackupDate), DateUtil.date(todayLocalDate)) == 0) {
            DateTime offsetMonth = DateUtil.offsetDay(DateUtil.date(nextWeekBackupDate), 7);
            return offsetMonth.toLocalDateTime().toLocalDate();
        }
        return nextWeekBackupDate;
    }

    /**
     * type == 3，自定义备份方式，根据开始时间以及自定义周期，计算下一次备份的时间，如果是今天则返回下一次的时间。
     *
     * @param startAt 开始时间
     * @param daily   天周期
     * @return 下一次备份的时间
     */
    public static LocalDate nextCustomBackupDateNotToday(Date startAt, int daily) {
        if (startAt == null) {
            return null;
        }
        LocalDate nextBackupDate = nextCustomBackupDate(startAt, daily);
        LocalDate todayLocalDate = DateUtil.date().toLocalDateTime().toLocalDate();

        // 如果当前的时间小于开始的时间，则直接返回开始的时间
        if (DateUtil.compare(DateUtil.date(nextBackupDate), DateUtil.date(todayLocalDate)) == 0) {
            DateTime offsetDay = DateUtil.offsetDay(DateUtil.date(nextBackupDate), daily);
            return offsetDay.toLocalDateTime().toLocalDate();
        }
        return nextBackupDate;
    }

    /**
     * 根据 type 类型的不同，获取不同方式的下一次备份的时间，但是不包含今天
     *
     * @param hBackupSetup 备份的配置数据
     * @return 下一次备份的时间
     */
    public static LocalDate nextBackupDateNotToday(HBackupSetup hBackupSetup) {
        return nextBackupDateNotToday(hBackupSetup.getType(), hBackupSetup.getStartAt(), hBackupSetup.getMonthly(), hBackupSetup.getWeekly(),
                hBackupSetup.getDaily());
    }

    /**
     * 根据 type 类型的不同，获取不同方式的下一次备份的时间，但是不包含今天
     *
     * @param backupSetup 请求参数的备份配置
     * @return 下一次备份的时间
     */
    public static LocalDate nextBackupDateNotToday(ResponseBackupSetup backupSetup) {
        DateTime parse = DateUtil.parse(backupSetup.getStartAt());
        LocalDate localDate = parse.toLocalDateTime().toLocalDate();
        return nextBackupDateNotToday(backupSetup.getType(), DateUtil.date(localDate).toJdkDate(), backupSetup.getMonthly(), backupSetup.getWeekly(),
                backupSetup.getDaily());
    }

    /**
     * 根据 type 类型的不同，获取不同方式的下一次备份的时间，但是不包含今天
     *
     * @param type    备份类型
     * @param startAt 开始时间
     * @param monthly 月周期
     * @param weekly  周周期
     * @param daily   天周期
     * @return 下一次备份的时间
     */
    public static LocalDate nextBackupDateNotToday(Short type, Date startAt, Integer monthly, Integer weekly, Integer daily) {
        type = type == null ? 0 : type;
        if (type == 1) {
            return nextMonthBackupDateNotToday(startAt, monthly);
        } else if (type == 2) {
            return nextWeekBackupDateNotToday(startAt, weekly);
        } else if (type == 3) {
            return nextCustomBackupDateNotToday(startAt, daily);
        }

        return null;
    }
}
